package sf.database.jdbc.sql;

import sf.common.wrapper.Page;
import sf.database.dao.DBContext;
import sf.database.dialect.DBDialect;
import sf.database.template.CType;
import sf.database.template.TemplateRender;
import sf.database.template.TemplateType;
import sf.database.util.DBUtils;
import sf.spring.util.Assert;
import sf.spring.util.CollectionUtils;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;

/**
 * 根据模板id的sql查询
 * @author sxf
 */
public class CrudTemplateImpl implements CrudTemplateInf {

    private static CrudTemplateImpl instance = new CrudTemplateImpl();
    private CrudSqlInf cs = CrudSqlImpl.getInstance();

    private CrudTemplateImpl() {

    }

    public static CrudTemplateImpl getInstance() {
        return instance;
    }

    public static SQLContext getBatchSqlContextById(TemplateType type, String sqlId, String dbType, List<Map<String, Object>> parameters, List<Object[]> paras) {
        SQLContext one = null;
        if (CollectionUtils.isNotEmpty(parameters)) {
            for (Map<String, Object> map : parameters) {
                SQLContext parsedSql = TemplateRender.getTemplateHandler(type).getParsedSql(sqlId, dbType, map);
                if (one == null) {
                    one = parsedSql;
                }
                paras.add(parsedSql.toObjectArray());
            }
        }
        if (one == null) {
            one = TemplateRender.getTemplateHandler(type).getParsedSql(sqlId, dbType, new HashMap<>());
        }
        return one;
    }

    public static SQLContext getBatchSqlContextBySqlSource(TemplateType type, String sqlSource, List<Map<String, Object>> parameters, List<Object[]> paras) {
        SQLContext one = null;
        if (CollectionUtils.isNotEmpty(parameters)) {
            for (Map<String, Object> map : parameters) {
                SQLContext parsedSql = TemplateRender.getTemplateHandler(type).getParsedSqlSource(sqlSource, map);
                if (one == null) {
                    one = parsedSql;
                }
                paras.add(parsedSql.toObjectArray());
            }
        }
        if (one == null) {
            one = TemplateRender.getTemplateHandler(type).getParsedSqlSource(sqlSource, null);
        }
        return one;
    }

    /**
     * 是否使用dialect处理,主要解决应用跨数据库访问的问题
     * @param conn
     * @return
     */
    public static String getDialectDBType(Connection conn) {
        DBDialect dialect = DBUtils.doGetDialect(conn, false);
        return dialect.getName();
    }

    protected static SQLContext batchParam(Connection conn, String sqlOrId, CType ctype, List<Map<String, Object>> parameters, List<Object[]> paras) {
        DBContext context = DBUtils.doGetDBContext(conn);
        TemplateType type = getTemplateType(context);
        SQLContext one = null;
        switch (ctype) {
            case id:
                String dbType = getDialectDBType(conn);
                one = getBatchSqlContextById(type, sqlOrId, dbType, parameters, paras);
                break;
            case source:
                one = getBatchSqlContextBySqlSource(type, sqlOrId, parameters, paras);
                break;
            default:
                break;
        }
        return one;
    }

    @Override
    public int[] executeBatch(Connection conn, String sqlOrId, CType ctype, List<Map<String, Object>> parameters) {
        List<Object[]> paras = new ArrayList<>();
        SQLContext one = batchParam(conn, sqlOrId, ctype, parameters, paras);
        return cs.executeBatch(conn, one.getSql(), paras);
    }

    @Override
    public int[] executeBatch(Connection conn, String sqlOrId, CType ctype, List<Map<String, Object>> parameters, boolean insertFast,
                              int batchSize, List<String> pkeys, List<Map<String, Object>> keyValues) {
        List<Object[]> paras = new ArrayList<>();
        SQLContext one = batchParam(conn, sqlOrId, ctype, parameters, paras);
        return cs.executeBatch(conn, one.getSql(), paras, insertFast, batchSize, pkeys, keyValues);
    }

    @Override
    public int insert(Connection conn, List<String> pkeys, Map<String, Object> keyValues, String sqlOrId, CType ctype, Map<String, Object> paramters) {
        SQLContext parsedSql = getSqlContext(conn, sqlOrId, ctype, paramters);
        return cs.execute(conn, parsedSql.getSql(), parsedSql.toObjectArray(), false, pkeys, keyValues);
    }

    @Override
    public int execute(Connection conn, String sqlOrId, CType ctype, Map<String, Object> paramters) {
        SQLContext parsedSql = getSqlContext(conn, sqlOrId, ctype, paramters);
        return cs.execute(conn, parsedSql.getSql(), parsedSql.toObjectArray());
    }

    @Override
    public <T> Page<T> selectPage(Connection conn, long start, int limit, Class<T> beanClass, String sqlOrId, CType ctype,
                                  Map<String, Object> paramters) {
        DBContext context = DBUtils.doGetDBContext(conn);
        TemplateType type = getTemplateType(context);
        SQLContext parsedSql = null;
        switch (ctype) {
            case id:
                String dbType = getDialectDBType(conn);
                parsedSql = TemplateRender.getTemplateHandler(type).getParsedPageSql(sqlOrId, dbType, paramters);
                break;
            case source:
                parsedSql = TemplateRender.getTemplateHandler(type).getParsedPageSqlSource(sqlOrId, paramters);
                break;
            default:
                break;
        }
//        SQLContext parsedPageListSql = TemplateRender.getTemplateHandler().getParsedPageListSql(sqlOrId, paramters);
        String listSql = parsedSql.getListSql();
        Object[] objects = parsedSql.toObjectArray();
        return cs.selectPageRaw(conn, start, limit, beanClass, parsedSql.getCountSql(), objects, listSql, objects);
    }

    @Override
    public <T> Page<T> selectPageRaw(Connection conn, long start, int limit, Class<T> beanClass, String countsqlOrId, CType countType, Map<String, Object> countParas,
                                     String listsqlOrId, CType listType, Map<String, Object> listParas) {
        Assert.notNull(beanClass, "beanClass cannot be null.");
        Assert.notNull(countsqlOrId, "countSql cannot be null.");
        Assert.notNull(listsqlOrId, "listSql cannot be null.");
        DBContext context = DBUtils.doGetDBContext(conn);
        TemplateType type = getTemplateType(context);
        String dbType = getDialectDBType(conn);
        SQLContext countSqlCtx = null;
        SQLContext listSqlCtx = null;
        switch (countType) {
            case id:
                countSqlCtx = TemplateRender.getTemplateHandler(type).getParsedSql(countsqlOrId, dbType, countParas);
                break;
            case source:
                countSqlCtx = TemplateRender.getTemplateHandler(type).getParsedPageSqlSource(countsqlOrId, countParas);
                break;
            default:
                break;
        }
        switch (listType) {
            case id:
                listSqlCtx = TemplateRender.getTemplateHandler(type).getParsedSql(listsqlOrId, dbType, listParas);
                break;
            case source:
                listSqlCtx = TemplateRender.getTemplateHandler(type).getParsedPageSqlSource(listsqlOrId, listParas);
                break;
            default:
                break;
        }
        return cs.selectPageRaw(conn, start, limit, beanClass, countSqlCtx.getSql(), countSqlCtx.toObjectArray(),
                listSqlCtx.getSql(), listSqlCtx.toObjectArray());
    }

    @Override
    public Object[] selectArray(Connection conn, String sqlOrId, CType ctype, Map<String, Object> paramters) {
        SQLContext parsedSql = getSqlContext(conn, sqlOrId, ctype, paramters);
        return cs.selectArray(conn, parsedSql.getSql(), parsedSql.toObjectArray());
    }

    @Override
    public <T> List<T> selectList(Connection conn, Class<T> beanClass, String sqlOrId, CType ctype, Map<String, Object> paramters) {
        SQLContext parsedSql = getSqlContext(conn, sqlOrId, ctype, paramters);
        return cs.selectList(conn, beanClass, parsedSql.getSql(), parsedSql.toObjectArray());
    }

    @Override
    public <T> List<T> selectList(Connection conn, Class<T> beanClass, String sqlOrId, CType ctype, Map<String, Object> paramters, long start, int limit) {
        SQLContext parsedSql = getSqlContext(conn, sqlOrId, ctype, paramters);
        String pageSql = DBUtils.doGetDialect(conn, false).sqlPageList(new StringBuilder(parsedSql.getSql()), start, limit).toString();
        return cs.selectList(conn, beanClass, pageSql, parsedSql.toObjectArray());
    }

    @Override
    public <T> T selectOne(Connection conn, Class<T> beanClass, String sqlOrId, CType ctype, Map<String, Object> paramters) {
        SQLContext parsedSql = getSqlContext(conn, sqlOrId, ctype, paramters);
        return cs.selectOne(conn, beanClass, parsedSql.getSql(), parsedSql.toObjectArray());
    }

    @Override
    public List<Map<String, Object>> select(Connection conn, String sqlOrId, CType ctype, Map<String, Object> paramters) {
        SQLContext parsedSql = getSqlContext(conn, sqlOrId, ctype, paramters);
        return cs.select(conn, parsedSql.getSql(), parsedSql.toObjectArray());
    }

    @Override
    public <T> void selectIterator(Connection conn, Consumer<Iterable<T>> ormIt, Class<T> returnClass, String sqlOrId, CType ctype, Map<String, Object> paramters) {
        SQLContext parsedSql = getSqlContext(conn, sqlOrId, ctype, paramters);
        Crud.getInstance().getCrudSql().selectIterator(conn, ormIt, returnClass, false, parsedSql.getSql(), parsedSql.toObjectArray());
    }

    @Override
    public <T> void selectStream(Connection conn, Consumer<Stream<T>> ormStream, Class<T> returnClass, String sqlOrId, CType ctype, Map<String, Object> paramters) {
        SQLContext parsedSql = getSqlContext(conn, sqlOrId, ctype, paramters);
        Crud.getInstance().getCrudSql().selectStream(conn, ormStream, returnClass, false, parsedSql.getSql(), parsedSql.toObjectArray());
    }

    protected static SQLContext getSqlContext(Connection conn, String sqlOrId, CType ctype, Map<String, Object> paramters) {
        DBContext context = DBUtils.doGetDBContext(conn);
        TemplateType type = getTemplateType(context);
        SQLContext parsedSql = null;
        switch (ctype) {
            case id:
                String dbType = getDialectDBType(conn);
                parsedSql = TemplateRender.getTemplateHandler(type).getParsedSql(sqlOrId, dbType, paramters);
                break;
            case source:
                parsedSql = TemplateRender.getTemplateHandler(type).getParsedSqlSource(sqlOrId, paramters);
                break;
            default:
                break;
        }
        return parsedSql;
    }

    protected static TemplateType getTemplateType(DBContext context) {
        TemplateType type = null;
        if (context != null) {
            type = context.getSqlTemplateType();
        }
        return type;
    }
}
